﻿using System;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Data;

namespace NullableNumericUpDownExample.Classes
{
    /// <summary>
    /// Extented NumericUpDown class. It supports nullable (numeric) data types like int?/double? etc. 
    /// There were also some some minor bugs assiociated with parsing big positive and negative values (mentioned here http://silverlight.codeplex.com/workitem/3767) 
    /// This is first public release (0.1). All suggestions are welcome. 
    /// Author Jakub Madej /ilektrik. 
    /// </summary>
    public class NullableNumericUpDown : NumericUpDown
    {
        bool _isFirstTimeLoad = true;
        bool _formatAsDefault = false;

        public NullableNumericUpDown() : base()
        {
            
        }

        protected override void OnParsing(UpDownParsingEventArgs<double> e)
        {
            // If content of NUD is erased then set the bound field to 0 (if its not nullable type) or to null (when it is nullalable)
            // On parsing error (user entered letters or special signs) - ignore input and show previous value. 
            if (e.Text.Trim() == "")
            {
                BindingExpression expression = this.GetBindingExpression(NullableNumericUpDown.ValueProperty);
                if (expression != null)
                {
                    _formatAsDefault = true;
                    object parent = null;
                    object value = expression.DataItem;
                    PropertyInfo pi = null;
                    bool nullable = false;

                    foreach (string path in expression.ParentBinding.Path.Path.Split(new char[] { '.' }))
                    {
                        pi = value.GetType().GetProperty(path);
                        System.Type valueType = value.GetType().GetProperty(path).PropertyType;
                        nullable = valueType.IsGenericType && valueType.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
                        parent = value;
                        value = value.GetType().GetProperty(path).GetValue(value, null);
                    }
                    if (nullable)
                    {
                        pi.SetValue(parent, null, null);
                    }
                    else
                    {
                        pi.SetValue(parent, 0, null);
                    }
                }
                e.Handled = true;
                return;
            }

            double tempResult;
            if (!double.TryParse(e.Text, out tempResult))
            {
                _formatAsDefault = true;
                e.Handled = false;
                return;
            }

            e.Handled = true;

            if (e.Value > this.Maximum)
                e.Value = this.Maximum;

            if (e.Value < this.Minimum)
                e.Value = this.Minimum;
        }

        
        // Refresh bound field - if maximum is smaller then bound field (will occur only on some special occasions)
        protected override void OnValueChanged(System.Windows.RoutedPropertyChangedEventArgs<double> e)
        {
            if (!_formatAsDefault)
            {
                if (e.OldValue > this.Maximum || e.OldValue < this.Minimum)
                {
                    BindingExpression expression = this.GetBindingExpression(NullableNumericUpDown.ValueProperty);
                    expression.UpdateSource();
                }

                base.OnValueChanged(e);
            }
        }

        // Set some nice formatting like "123 345,31"
        protected override string FormatValue()
        {
            if (DesignerProperties.IsInDesignTool)
            {
                return "0,00";
            }
            if (_isFirstTimeLoad || _formatAsDefault)
            {
                _isFirstTimeLoad = false;
                _formatAsDefault = false;

                string mask = "";

                BindingExpression expression = this.GetBindingExpression(NullableNumericUpDown.ValueProperty);
                if (expression != null)
                {
                    object value = expression.DataItem;
                    string[] paths = expression.ParentBinding.Path.Path.Split(new char[] { '.' });
                    foreach (string path in paths)
                    {
                        value = value.GetType().GetProperty(path).GetValue(value, null);
                    }
                    if (value == null) return mask;
                }

            }

            string format = "{0:#,0";
            string decimalPlaces = string.Empty;
            for (int i = 0; i < this.DecimalPlaces; i++)
            {
                decimalPlaces += "0";
            }
            if (this.DecimalPlaces > 0)
            {
                format += ".";
                format += decimalPlaces;
            }
            format += "}";
            return string.Format(format, this.Value);
        }
    }
}
